//BeanCipher - Bean Cipher Security Layer
//Copyright (C) 2005 Raul Alfredo Bajales
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR 2A PARTICULAR PURPOSE. See the GNU2
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package ar.com.rab.beancipher.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ar.com.rab.beancipher.spi.IBeanCipher;
import ar.com.rab.beancipher.spi.IConstants;
import ar.com.rab.beancipher.spi.exceptions.BeanCipherException;

/**
 * Abstract implementation of IBeanCipher interface that manage bean state 
 * to avoid multiple cipherings of a bean.
 * 
 * @author Bajales Raul
 */
public abstract class AbstractBeanCipher implements IBeanCipher {

    private static Log log = LogFactory.getLog(AbstractBeanCipher.class);
    private BeanStateManager beanStateManager;

    /**
     * Constructor1
     * 
     */
    public AbstractBeanCipher() {
        super();
        this.beanStateManager = new BeanStateManager();
    }
    
    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.IBeanCipher#cipherBean(java.lang.Object)
     */
    public void cipherBean(Object aBean) throws BeanCipherException {
        try {
            log.debug("trying to cipher an instance of " + aBean.getClass().getName());
            if (isBeanCiphered(aBean)) {
                log.debug("bean already ciphered!");
                return;
            }
            doCipherBean(aBean);
            this.beanStateManager.updateBeanState(aBean, true);
        } catch (Exception e) {
            throw new BeanCipherException(buildExceptionMessage(aBean, "cipher"), e);
        }
    }
    
    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.IBeanCipher#deCipherBean(java.lang.Object)
     */
    public void deCipherBean(Object aBean) throws BeanCipherException {
        try {
            log.debug("trying to de-cipher an instance of " + aBean.getClass().getName());
            if (!isBeanCiphered(aBean)) {
                log.debug("bean already de-ciphered!");
                return;
            }
            doDeCipherBean(aBean);
            this.beanStateManager.updateBeanState(aBean, false);
        } catch (Exception e) {
            throw new BeanCipherException(buildExceptionMessage(aBean, "de-cipher"), e);
        }
    }
    
    /**
     * @param aBean
     * @param action
     * @return String
     */
    private String buildExceptionMessage(Object aBean, String action) {
        String message = "Can't " + action + " because the bean is null";
        if (aBean != null) message = "Can't " + action + " bean of class '" + aBean.getClass().getName() + "'";
        return message;
    }

    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.IBeanCipher#isBeanCiphered(java.lang.Object)
     */
    public boolean isBeanCiphered(Object aBean) throws BeanCipherException {
        return (this.beanStateManager.getBeanState(aBean) == IConstants.IS_CIPHERED);
    }

    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.IBeanCipher#reloadConfiguration()
     */
    public abstract void reloadConfiguration() throws BeanCipherException;

    /**
     * Forwards the cipher operation to the extending implementation 
     * 
     * @see Template Pattern (GoF)
     * @param aBean
     */
    protected abstract void doCipherBean(Object aBean) throws BeanCipherException;

    /**
     * Forwards the decipher operation to the extending implementation
     *  
     * @see Template Pattern (GoF)
     * @param aBean
     */
    protected abstract void doDeCipherBean(Object aBean) throws BeanCipherException;
    
    /**
     * @author Bajales Raul
     * 
     * Manage bean states (CIPHERED or DE_CIPHERED)
     */
    private class BeanStateManager {
        
        /**
         * List of ciphereds beans references
         * 
         * <code>ciphereds</code>
         */
        private List ciphereds;
        
        /**
         * Constructor
         */
        public BeanStateManager() {
            super();
            this.ciphereds = new ArrayList();
        }

        /**
         * @param aBean
         * @param ciphered
         */
        public void updateBeanState(Object aBean, boolean ciphered) {
            int state = getBeanState(aBean);
            if ((state == IConstants.IS_CIPHERED) && ciphered) return;
            if ((state == IConstants.IS_DE_CIPHERED) && !ciphered) return;
            if (!ciphered) 
                this.ciphereds.remove(getCipheredsReferenceIndex(aBean));
            else
                this.ciphereds.add(aBean);
        }

        /**
         * @param bean
         * @return int
         */
        public int getBeanState(Object aBean) {
            int idx = getCipheredsReferenceIndex(aBean);
            if (idx == -1) return IConstants.IS_DE_CIPHERED;
            return IConstants.IS_CIPHERED;
        }
        
        /**
         * @param aBean
         * @return int
         */
        public int getCipheredsReferenceIndex(Object aBean) {
            for (int i = 0; i < this.ciphereds.toArray().length; i++) {
                Object element = this.ciphereds.toArray()[i];
                if (aBean == element) return i;            
            }
            return -1;
        }
        
        
    }

}
